{- TODO:
  - Ajustar parser para fornecer "lineNumber"
  - [Possivelmente] Ajustar o valor "sourceCode" de "rootToken" (para mostrar o fonte todo do programa)
  - Emitir erros quando se tenta aplicar um patch a árvores de estrutura diferente
  - Ajustar a função de patch para ela ser mais útil (ie. implementar serialização de patches)
-}

module SourceData (Token (Token)
                  , Source
                  , Patch
                  , ChangedCode
                  , Program (Program)
                  , diffCode
                  , diffCode'
                  , patchTree
                  , Differ ((=/=)) -- exportado somente para debugging (mas "no harm done" nisso)
                  ) where

import Data.Tree

data Token = Token {
      lineNumber   :: Int -- Nota: Armazenamento do número não foi implementada ainda
    , sourceCode   :: String
    , tokenType    :: String
    } deriving(Eq, Read)

type Source = Tree Token
type Patch = Tree (Either (Token, Token) Token) -- Patch vai de uma árvore à outra

newtype ChangedCode = ChangedCode (Either (Token, Token) Token) -- Declaração de newtype para implemetar "show"
newtype Program = Program (Tree ChangedCode) -- Declaração de newtype para implemetar "show"

instance Show ChangedCode where -- Mostra diferenças de códigos
    show (ChangedCode (Left (a,b))) = "\nChange in " ++ show (tokenType a) ++ ", which is now a " ++ show (tokenType b) ++ "\n" ++  "-- " ++ show (sourceCode a) ++ "\n" ++ "++ " ++ show (sourceCode b) ++ "\n\n"
    show _ = ""

instance Show Token where
    show = sourceCode

instance Show Program where
    show (Program a) = drawTree $ fmap show a

-- Token raiz, representa o programa como um todo. 
rootToken :: Token
rootToken = Token {
              lineNumber = 0
            , sourceCode = ""
            , tokenType  = "Root"
            }
-- Representa um valor vazio/ausência de código. Possivelmente desnecessário.
zeroToken :: Token
zeroToken = rootToken {tokenType = "NullToken"}

-- Representa o código vazio. Possivelmente desnecessário.
zeroSource :: Source
zeroSource = Node zeroToken []


-- Difere dois códigos e imprime
diffCode :: Source -> Source -> Program
diffCode a = Program . fmap ChangedCode . diffCode' a

-- Idem acima, não propício para impressão
diffCode' :: Source -> Source -> Patch
diffCode' = zipWithTree2 (=/=) zeroSource
--diffCode' = zipWithTree2 (=/=)

-- Typeclass que define tipos "diferenciaveis" (dados a, b, gera Either (a,b))
class Eq a => Differ a where
    (=/=) :: a -> a -> Either (a, a) a

-- Um Token é a versão recente ou a antiga e a recente
instance Differ Token where
    a =/= b =
        if a == b
           then Right a
           else Left (a, b)

--ZipWith2 generalizado para árvores. Assume árvores de mesma estrutura.
--zipWithTree2 :: (a -> a -> (Either (a,a) b)) -> Tree a -> Tree a -> Tree a -> Tree b
zipWithTree2 f z (Node a []) (Node b  bs) = Node
                                            (f a b)
                                            (zipWith (zipWithTree2 f z) [z] bs)
zipWithTree2 f z (Node a  as) (Node b []) = Node
                                            (f a b)
                                            (zipWith (zipWithTree2 f z) as [z])
zipWithTree2 f z (Node a as) (Node b bs)  = Node
                                            (f a b)
                                            (zipWith (zipWithTree2 f z) as bs)


--ZipWith2 generalizado para árvores.
--zipWithTree2 :: (a -> a -> (Either (a,a) a))
-- zipWithTree2 f (Node a []) (Node b  []) = Node
--                                             (f a b) []
-- zipWithTree2 f (Node a []) (Node b  bs) = Node
--                                             (f a b)
--                                             (zipWith (zipWithTree2 f) [] bs)
-- zipWithTree2 f (Node a  as) (Node b []) = Node
--                                             (f a b)
--                                             (zipWith (zipWithTree2 f) as [])
-- zipWithTree2 f (Node a as) (Node b bs)  = Node
--                                             (f a b)
--                                             (zipWith (zipWithTree2 f) as bs)


-- Aplica um patch a um código
patchTree :: Source -> Patch -> Source
patchTree (Node a as) (Node (Right _) ns) =
   Node a (zipWith patchTree as ns)
    
patchTree (Node a as) (Node (Left (old, new)) ns) =
    Node (subsValue (== old) new a) (zipWith patchTree as ns)
    where
      subsValue :: (a -> Bool) -> a -> a-> a
      subsValue test new value =
          if test value
            then new
            else value

